# coding: utf-8
import sys,os,inspect
import numpy as np
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
sys.path.insert(0,parentdir) 
from common.functions import *
from common.gradient import numerical_gradient


class TwoLayerNet:#两层权重，三层节点
# hidden_size指的是节点的数量,
# 这里假设每层的hidden_size都是一致的
    def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
        # 随机初始化权重,偏置初始为0
        self.params = {}
        self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)#在第一层共有input_size*hidden_size个权重
        self.params['b1'] = np.zeros(hidden_size)#在第一层共有hidden_size个偏置

        # 所谓的第一层指的是输入点(不含激活函数),输入的权重和偏置,以及第一层的带有激活函数的hidden_unit_layer

        self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)#在第一层共有hidden_size*output_size个权重
        self.params['b2'] = np.zeros(output_size)#第二层的偏置数量=输出节点数量

    def predict(self, x):
        W1, W2 = self.params['W1'], self.params['W2']
        b1, b2 = self.params['b1'], self.params['b2']
    
        a1 = np.dot(x, W1) + b1
        z1 = sigmoid(a1)#z1是第1层的输出节点

        a2 = np.dot(z1, W2) + b2#z2是第2层的输出节点
        y = softmax(a2)#a2是输出节点,y是最终输出节点.
        
        return y
        
    # x:输入数据, t:监督数据
    def loss(self, x, t):
        y = self.predict(x)
        
        return cross_entropy_error(y, t)
    
    def accuracy(self, x, t):
        y = self.predict(x)
        y = np.argmax(y, axis=1)
        t = np.argmax(t, axis=1)
        
        accuracy = np.sum(y == t) / float(x.shape[0])
        return accuracy
        
    # x:输入数据, t:监督数据
    def numerical_gradient(self, x, t):
        loss_W = lambda W: self.loss(x, t)
        
        grads = {}
        grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
        
        return grads

        # x:整个神经网络的输入.
        # y:整个神经网络的最终输出.


# 下面这个函数提供的是
# wji=wji−ε△w中的△w
# 这里的△w包含所有层的权重ｗｉｊ以及各层的ｂ的大小
# 以下代码是在实现反向传播原理，具体解释参考：
# https://blog.csdn.net/appleyuchi/article/details/91049397
    def gradient(self, x, t):
        W1, W2 = self.params['W1'], self.params['W2']
        b1, b2 = self.params['b1'], self.params['b2']
        grads = {}
        
        batch_num = x.shape[0]#batch_num=100
        
        # forward,误差前向传播
        a1 = np.dot(x, W1) + b1
        z1 = sigmoid(a1)
        # z1是第１层的输出
        a2 = np.dot(z1, W2) + b2
        # ａ２是第２层的输出
        y = softmax(a2)
        # print("y=",y)
        # ｙ是整个神经网络最终的输出

        # backward
        dy = (y - t) / batch_num#可以理解为平均每个样本会导致多大的误差
        # ｙ－ｔ＝Ｅ
        # ｄｙ＝∂E/∂xin
        # batch_num=100
        # print("dy.shape",dy.shape)　#(100, 10)

        # print("z1.T.shape",z1.T.shape)#(50, 100)
        grads['W2'] = np.dot(z1.T, dy)#这个ｄｏｔ函数是用来进行线代中的矩阵运算的

        grads['b2'] = np.sum(dy, axis=0)
        #用来弥补ｙ－ｔ之间的差值，以便于让Ｅ趋向于０
        #因为第一层的输出中的每个ｙｉ都要乘以两层之间的ｗｉｊ，各自都有一个ｂｉｊ
        # 所以把１００个bij叠加起来就是输入到下一层的某个节点的偏置(ｂｊ, j∈[1,10])

        da1 = np.dot(dy, W2.T)
        dz1 = sigmoid_grad(a1) * da1
        grads['W1'] = np.dot(x.T, dz1)
        grads['b1'] = np.sum(dz1, axis=0)
        # 对ｂ进行输出，可以看到ｂ的维度是５０
        return grads
